﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace DecisionTree
{
    public class Forest
    {
        public int TrainError { get; set; }
        public int TestError { get; set; }

        public int NumTree { get; private set; }
        public int NumRecordsEachTree { get; private set; }

        public C45Learning[] learnings { get; private set; }
        public DecisionTree[] trees { get; private set; }

        private Random rnd = new Random();

        public int[][] RandomSubset(int numTree, int numRecordsEachTree)
        {
            this.NumTree = numTree;

            int total = Parameters.TotalRecordsTraining;

            int[][] rVal = new int[numTree][];

            for (int i = 0; i < numTree; i++)
            {
                rVal[i] = new int[numRecordsEachTree];
                for (int j = 0; j < numRecordsEachTree; j++)
                    rVal[i][j] = rnd.Next(total);
            }

            return rVal;
        }

        public Forest(int numTree, int numRecordsEachTree)
        {
            this.NumTree = numTree;
            this.NumRecordsEachTree = numRecordsEachTree;

            var testInputs = FileOperations.ReadImages(Parameters.TestInputPath);
            var testOutputs = FileOperations.ReadLabels(Parameters.TestOutputPath);

            int[][] idxs  = RandomSubset(numTree, numRecordsEachTree);

            var inputs = FileOperations.ReadImages(Parameters.TrainInputPath);
            var outputs = FileOperations.ReadLabels(Parameters.TrainOutputPath);

            learnings = new C45Learning[numTree];
            trees = new DecisionTree[numTree];

            for (int i = 0; i < idxs.Length; i++)
            {
                Console.WriteLine(i.ToString() + "/" + idxs.Length.ToString());

                DecisionTree tree = new DecisionTree(Parameters.InputRanges, Parameters.OutputClasses, i);
                C45Learning learning = new C45Learning(tree);
                 
                tree.TrainError  = learning.Run(inputs, outputs, idxs[i]);
                tree.TestError = learning.ComputeError(testInputs, testOutputs);

                trees[i] = tree;
                learnings[i] = learning;                
            }

            this.TrainError = this.ComputeError(Parameters.TrainInputPath, Parameters.TrainOutputPath);
            this.TestError = this.ComputeError(Parameters.TestInputPath, Parameters.TestOutputPath);
            this.Log(Parameters.LogPath);
        }

        public Forest(int numTree, string trainInputPath, string trainOutputPath)
        {
            this.NumTree = numTree;

            int numRecordsEachTree = Parameters.TotalRecordsTraining / numTree;

            learnings = new C45Learning[numTree];
            trees = new DecisionTree[numTree];

            for (int i = 0; i < numTree; i++)
            {
                DecisionTree tree = new DecisionTree(Parameters.InputRanges, Parameters.OutputClasses, i);

                byte[][] trainInputs = FileOperations.ReadImages(trainInputPath, numRecordsEachTree * i, numRecordsEachTree);
                byte[] trainOutputs = FileOperations.ReadLabels(trainOutputPath, numRecordsEachTree * i, numRecordsEachTree);

                C45Learning learning = new C45Learning(tree);
                learning.Run(trainInputs, trainOutputs);

                trees[i] = tree;
                learnings[i] = learning;
            }
        }

        private int Compute(byte[] record)
        {
            byte[] results = new byte[Parameters.OutputClasses];
           
            for (int i = 0; i < trees.Length; i++)
            {
                int result = trees[i].Compute(record);
                results[result]++;
            }

            int majority = results.MaxIndex();

            return majority;
        }

        //private int ComputeWithHistogram(byte[] record)
        //{
        //    double[] histogramSum = new double[Parameters.OutputClasses];

        //    for (int i = 0; i < trees.Length; i++)
        //    {
        //        double[] histogram = trees[i].ComputeHistogram(record);
        //        histogramSum = Tools.SumVectors(histogramSum, histogram);
        //    }

        //    int majority = histogramSum.MaxIndex();
        //    return majority;
        //}

        public int ComputeError(string testInputPath, string testOutputPath)
        {
            int miss = 0;

            byte[][] testInputs = FileOperations.ReadImages(testInputPath);
            byte[] testOutputs = FileOperations.ReadLabels(testOutputPath);

            for (int i = 0; i < testInputs.Length; i++)
            {
                int result = Compute(testInputs[i]);
                if (result != testOutputs[i])
                    miss++;
            }

            return miss;
        }

        public void Log(string path)
        {
            using (StreamWriter writer = File.CreateText(path))
            {
                writer.WriteLine("Date: " + DateTime.Now.ToString());
                writer.WriteLine("Forest number of trees: " + this.NumTree);
                writer.WriteLine("Forest number of records each tree: " + this.NumRecordsEachTree);
                writer.WriteLine("Forest train error: " + this.TrainError);
                writer.WriteLine("Forest test error: " + this.TestError);
                writer.WriteLine();

                writer.WriteLine(Parameters.Log());

                for (int i = 0; i < this.NumTree; i++)
                {
                    this.trees[i].Log(writer);
                    writer.WriteLine();
                }                
            }
        }
    }
}
